package com.ideaaedi.springcloud.jd.commonspring.util;


import com.alibaba.fastjson2.JSON;
import com.ideaaedi.springcloud.jd.commonds.constant.BaseConstant;
import com.ideaaedi.springcloud.jd.commonds.entity.BaseCodeMsgEnum;
import com.ideaaedi.springcloud.jd.commonds.enums.SpecUserTenantEnum;
import com.ideaaedi.springcloud.jd.commonds.exception.BaseException;
import com.ideaaedi.springcloud.jd.commonspring.provider.UserIdTenantDataScopeProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 上下文工具
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2021.0.1.A
 */
@Slf4j
public final class JdContextUtil {
    
    /**
     * 获取当前登录用户id
     *
     * @return 当前登录用户id
     */
    @Nullable
    public static Long currUserId() {
        HttpServletRequest httpServletRequest = getHttpServletRequest();
        Long userId = null;
        if (httpServletRequest != null) {
            Object userIdObj = httpServletRequest.getAttribute(BaseConstant.CURR_USER_ID);
            if (userIdObj != null) {
                userId = Long.parseLong(userIdObj.toString());
            }
        }
        if (userId != null) {
            return userId;
        }
        Object obj = currPrincipal();
        if (!(obj instanceof UserIdTenantDataScopeProvider)) {
            return null;
        }
        UserIdTenantDataScopeProvider userIdTenantProvider = (UserIdTenantDataScopeProvider)obj;
        userId = userIdTenantProvider.obtainUserId();
        if (userId != null) {
            settingUserIdAndTenantIfMiss(userId, userIdTenantProvider.obtainTenant(),
                    userIdTenantProvider.distinctReadDataScopePaths(), userIdTenantProvider.distinctUpdateDataScopePaths());
        }
        return userId;
    }
    
    /**
     * 获取当前登录用户id
     *
     * @return 当前登录用户id
     */
    @NonNull
    public static Long currUserIdNonNull() {
        final Long currUserId = currUserId();
        Objects.requireNonNull(currUserId, "currUserId should not be null.");
        return currUserId;
    }
    
    /**
     * 获取当前登录用户所属租户
     *
     * @return 当前登录用户所属租户
     */
    @Nullable
    public static String currUserTenant() {
        HttpServletRequest httpServletRequest = getHttpServletRequest();
        String tenant = null;
        if (httpServletRequest != null) {
            Object tenantObj = httpServletRequest.getAttribute(BaseConstant.CURR_USER_TENANT);
            if (tenantObj != null) {
                tenant = tenantObj.toString();
            }
        }
        if (tenant != null) {
            return tenant;
        }
        Object obj = currPrincipal();
        if (!(obj instanceof UserIdTenantDataScopeProvider)) {
            return null;
        }
        UserIdTenantDataScopeProvider userIdTenantProvider = (UserIdTenantDataScopeProvider)obj;
        tenant = userIdTenantProvider.obtainTenant();
        Long userId = userIdTenantProvider.obtainUserId();
        if (userId != null && tenant != null) {
            settingUserIdAndTenantIfMiss(userId, userIdTenantProvider.obtainTenant(),
                    userIdTenantProvider.distinctReadDataScopePaths(), userIdTenantProvider.distinctUpdateDataScopePaths());
        }
        return tenant;
    }
    
    /**
     * 获取当前登录用户所属租户
     *
     * @return 当前登录用户所属租户
     */
    @NonNull
    public static String currUserTenantNonNull() {
        final String tenant = currUserTenant();
        if (StringUtils.isBlank(tenant)) {
            throw new BaseException(BaseCodeMsgEnum.OBTAIN_USER_TENANT_FAIL);
        }
        return tenant;
    }
    
    /**
     * 获取当前登录用户可读数据范围（deptId对应的deptPath的集合）
     *
     * @return 获取当前登录用户可读数据范围（deptId对应的deptPath的集合）
     */
    @Nullable
    public static LinkedHashSet<String> currUserDataScopeRead() {
        HttpServletRequest httpServletRequest = getHttpServletRequest();
        String readDataScopePathsStr = null;
        if (httpServletRequest != null) {
            Object tenantObj = httpServletRequest.getAttribute(BaseConstant.CURR_USER_READ_DATA_SCOPE_PATHS);
            if (tenantObj != null) {
                readDataScopePathsStr = tenantObj.toString();
            }
        }
        if (readDataScopePathsStr != null) {
            return new LinkedHashSet<>(JSON.parseArray(readDataScopePathsStr, String.class));
        }
        Object obj = currPrincipal();
        if (!(obj instanceof UserIdTenantDataScopeProvider)) {
            return null;
        }
        UserIdTenantDataScopeProvider userIdTenantProvider = (UserIdTenantDataScopeProvider)obj;
        Long userId = userIdTenantProvider.obtainUserId();
        String tenant = userIdTenantProvider.obtainTenant();
        LinkedHashSet<String> readDataScopePaths = userIdTenantProvider.distinctReadDataScopePaths();
        if (userId != null && tenant != null) {
            LinkedHashSet<String> updateDataScopePaths = userIdTenantProvider.distinctUpdateDataScopePaths();
            settingUserIdAndTenantIfMiss(userId, tenant, readDataScopePaths, updateDataScopePaths);
        }
        return readDataScopePaths;
    }
    
    /**
     * 获取当前登录用户可读数据范围（deptId对应的deptPath的集合）
     *
     * @return 获取当前登录用户可读数据范围（deptId对应的deptPath的集合）
     */
    @Nullable
    public static LinkedHashSet<String> currUserDataScopeUpdate() {
        HttpServletRequest httpServletRequest = getHttpServletRequest();
        String updateDataScopePathsStr = null;
        if (httpServletRequest != null) {
            Object tenantObj = httpServletRequest.getAttribute(BaseConstant.CURR_USER_READ_DATA_SCOPE_PATHS);
            if (tenantObj != null) {
                updateDataScopePathsStr = tenantObj.toString();
            }
        }
        if (updateDataScopePathsStr != null) {
            return new LinkedHashSet<>(JSON.parseArray(updateDataScopePathsStr, String.class));
        }
        Object obj = currPrincipal();
        if (!(obj instanceof UserIdTenantDataScopeProvider)) {
            return null;
        }
        UserIdTenantDataScopeProvider userIdTenantProvider = (UserIdTenantDataScopeProvider)obj;
        Long userId = userIdTenantProvider.obtainUserId();
        String tenant = userIdTenantProvider.obtainTenant();
        LinkedHashSet<String> updateDataScopePaths = userIdTenantProvider.distinctUpdateDataScopePaths();
        if (userId != null && tenant != null) {
            LinkedHashSet<String> readDataScopePaths = userIdTenantProvider.distinctReadDataScopePaths();
            settingUserIdAndTenantIfMiss(userId, tenant, readDataScopePaths, updateDataScopePaths);
        }
        return updateDataScopePaths;
    }
    
    /**
     * 获取当前http请求
     */
    public static HttpServletRequest getHttpServletRequest() {
        final RequestAttributes requestAttributes;
        try {
            requestAttributes = RequestContextHolder.currentRequestAttributes();
        } catch (IllegalStateException e) {
            log.debug(e.getMessage());
            return null;
        }
        if (!(requestAttributes instanceof ServletRequestAttributes)) {
            return null;
        }
        return ((ServletRequestAttributes) requestAttributes).getRequest();
    }
    
    /**
     * 获取当前http响应
     */
    public static HttpServletResponse getHttpServletResponse() {
        final RequestAttributes requestAttributes;
        try {
            requestAttributes = RequestContextHolder.currentRequestAttributes();
        } catch (IllegalStateException e) {
            log.debug(e.getMessage());
            return null;
        }
        if (!(requestAttributes instanceof ServletRequestAttributes)) {
            return null;
        }
        return ((ServletRequestAttributes) requestAttributes).getResponse();
    }
    
    /**
     * 设置用户id、租户到请求attributes
     *
     * @see JdContextUtil#settingUserIdAndTenantIfMiss(Long, String, Collection, Collection)
     */
    public static void settingUserIdAndTenantIfMiss(@NonNull SpecUserTenantEnum specUserTenant) {
        JdContextUtil.settingUserIdAndTenantIfMiss(specUserTenant.getUserId(), specUserTenant.getTenant(),
                specUserTenant.getReadDataScopePaths(), specUserTenant.getUpdateDataScopePaths());
    }
    
    /**
     * 设置用户id、租户到请求attributes
     *
     * @param userId 用户id
     * @param tenant 用户所属租户
     * @param readDataScopePaths 可读数据范围的deptId对应的deptPath
     * @param updateDataScopePaths 可写(修改/删除)数据范围的deptId对应的deptPath
     */
    public static void settingUserIdAndTenantIfMiss(@Nullable Long userId, @Nullable String tenant,
                                                    @Nullable Collection<String> readDataScopePaths,
                                                    @Nullable Collection<String> updateDataScopePaths) {
        settingUserIdAndTenantIfMiss(userId, tenant, JSON.toJSONString(readDataScopePaths), JSON.toJSONString(updateDataScopePaths));
    }
    
    /**
     * 设置用户id、租户到请求attributes
     *
     * @param userId 用户id
     * @param tenant 用户所属租户
     * @param readDataScopePathJsonArrStr 可读数据范围的deptId对应的deptPath 的json数组字符串形式
     * @param updateDataScopePathJsonArrStr 可写(修改/删除)数据范围的deptId对应的deptPath 的json数组字符串形式
     */
    public static void settingUserIdAndTenantIfMiss(@Nullable Long userId, @Nullable String tenant,
                                                    @Nullable String readDataScopePathJsonArrStr,
                                                    @Nullable String updateDataScopePathJsonArrStr) {
        if (userId == null) {
            return;
        }
        HttpServletRequest request = getHttpServletRequest();
        if (request == null) {
            request = new MockHttpServletRequest();
            ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request);
            RequestContextHolder.setRequestAttributes(servletRequestAttributes);
            request = getHttpServletRequest();
            request.setAttribute(BaseConstant.CURR_USER_ID, userId);
            if (tenant != null) {
                request.setAttribute(BaseConstant.CURR_USER_TENANT, tenant);
            }
            if (StringUtils.isNotBlank(readDataScopePathJsonArrStr)) {
                request.setAttribute(BaseConstant.CURR_USER_READ_DATA_SCOPE_PATHS, readDataScopePathJsonArrStr);
            }
            if (StringUtils.isNotBlank(updateDataScopePathJsonArrStr)) {
                request.setAttribute(BaseConstant.CURR_USER_UPDATE_DATA_SCOPE_PATHS, updateDataScopePathJsonArrStr);
            }
        } else {
            Long existUserId = (Long) request.getAttribute(BaseConstant.CURR_USER_ID);
            String existTenant = (String) request.getAttribute(BaseConstant.CURR_USER_TENANT);
            if (existUserId == null && existTenant == null) {
                request.setAttribute(BaseConstant.CURR_USER_ID, userId);
                if (tenant != null) {
                    request.setAttribute(BaseConstant.CURR_USER_TENANT, tenant);
                }
                if (StringUtils.isNotBlank(readDataScopePathJsonArrStr)) {
                    request.setAttribute(BaseConstant.CURR_USER_READ_DATA_SCOPE_PATHS, readDataScopePathJsonArrStr);
                }
                if (StringUtils.isNotBlank(updateDataScopePathJsonArrStr)) {
                    request.setAttribute(BaseConstant.CURR_USER_UPDATE_DATA_SCOPE_PATHS, updateDataScopePathJsonArrStr);
                }
            } else {
                log.info("Skip settingUserIdAndTenantIfMiss. Already existUserId -> {}, existTenant -> {}.",
                        existUserId, existTenant);
            }
        }
    }
    
    /**
     * 清除用户id、租户、可读数据范围、可写数据范围
     *
     * @param specUserTenant 指定清楚用户与租户(注：当不为空时，则匹配此值才清除)
     */
    public static void clear(@Nullable SpecUserTenantEnum specUserTenant) {
        HttpServletRequest request = getHttpServletRequest();
        if (request == null) {
           return;
        }
        Long existUserId = (Long) request.getAttribute(BaseConstant.CURR_USER_ID);
        String existTenant = (String) request.getAttribute(BaseConstant.CURR_USER_TENANT);
        if (specUserTenant == null) {
            request.removeAttribute(BaseConstant.CURR_USER_ID);
            request.removeAttribute(BaseConstant.CURR_USER_TENANT);
            request.removeAttribute(BaseConstant.CURR_USER_READ_DATA_SCOPE_PATHS);
            request.removeAttribute(BaseConstant.CURR_USER_UPDATE_DATA_SCOPE_PATHS);
            return;
        }
        if ((Long.valueOf(specUserTenant.getUserId())).equals(existUserId) && specUserTenant.getTenant().equals(existTenant)) {
            request.removeAttribute(BaseConstant.CURR_USER_ID);
            request.removeAttribute(BaseConstant.CURR_USER_TENANT);
            request.removeAttribute(BaseConstant.CURR_USER_READ_DATA_SCOPE_PATHS);
            request.removeAttribute(BaseConstant.CURR_USER_UPDATE_DATA_SCOPE_PATHS);
        } else {
            log.warn("clear userId and tenant failure. existUserId -> {}, specUserId -> {}, existTenant -> {}, specTenant -> {}.",
                    existUserId, specUserTenant.getUserId(), existTenant, specUserTenant.getTenant());
        }
    }
    
    /**
     * 清除用户id、租户、可读数据范围、可写数据范围，并返回清空的值
     * @return <ul>
     *     <li>左-左：用户id</li>
     *     <li>左-右：租户值</li>
     *     <li>右-左：可读数据范围</li>
     *     <li>右-右：可写数据范围</li>
     * </ul>
     */
    @Nullable
    public static Pair<Pair<Long, String>, Pair<String, String>> clearAndGet() {
        HttpServletRequest request = getHttpServletRequest();
        if (request == null) {
            return null;
        }
        // 获取
        Long userId = Optional.ofNullable(request.getAttribute(BaseConstant.CURR_USER_ID))
                .map(String::valueOf)
                .map(Long::parseLong)
                .orElse(null);
        String tenant = Optional.ofNullable(request.getAttribute(BaseConstant.CURR_USER_TENANT))
                .map(String::valueOf)
                .orElse(null);
        String readDataScope = Optional.ofNullable(request.getAttribute(BaseConstant.CURR_USER_READ_DATA_SCOPE_PATHS))
                .map(String::valueOf)
                .orElse(null);
        String updateDataScope = Optional.ofNullable(request.getAttribute(BaseConstant.CURR_USER_UPDATE_DATA_SCOPE_PATHS))
                .map(String::valueOf)
                .orElse(null);
        // 移除
        request.removeAttribute(BaseConstant.CURR_USER_ID);
        request.removeAttribute(BaseConstant.CURR_USER_TENANT);
        request.removeAttribute(BaseConstant.CURR_USER_READ_DATA_SCOPE_PATHS);
        request.removeAttribute(BaseConstant.CURR_USER_UPDATE_DATA_SCOPE_PATHS);
        return Pair.of(
                Pair.of(userId, tenant),
                Pair.of(readDataScope, updateDataScope)
        );
    }
    
    /**
     * 获取security上下文中的当前用户信息
     *
     * @return security上下文中的当前用户信息
     */
    @Nullable
    public static <T> T currPrincipal() {
        Authentication authentication =  currAuth();
        if (authentication == null) {
            return null;
        }
        //noinspection unchecked
        return (T)authentication.getPrincipal();
    }
    
    /**
     * 获取当前用户的授权信息（本系统中即为角色信息）
     *
     * @return 当前用户的授权信息（本系统中即为角色信息）
     */
    @NonNull
    public static Set<String> currAuthorities() {
        Authentication authentication =  currAuth();
        if (authentication == null) {
            return Collections.emptySet();
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        if (CollectionUtils.isEmpty(authorities)) {
            return Collections.emptySet();
        }
        return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
    }
    
    /**
     * 获取当前用户的认证鉴权信息
     *
     * @return 当前用户的认证鉴权信息
     */
    @Nullable
    public static Authentication currAuth() {
        SecurityContext context = SecurityContextHolder.getContext();
        if (context == null) {
            return null;
        }
        return context.getAuthentication();
    }
}